#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<wait.h>
#include <netinet/in.h>//bind
#include <arpa/inet.h>//bind
using namespace std;

const uint16_t default_port = 8080;
class UdpServer
{
public:
    UdpServer(uint16_t port = default_port) 
    :_listen_sockfd(-1)
    , _port(port)
    , _ip()
    {}

    bool InitServer()
    {
        //1. 创建套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(_listen_sockfd < 0){
            cerr<< "socket error" << endl;
            return false;//创建失败
        }

        //cout<<"socket create success, sockfd: "<<_sockfd<<endl;

        //2. 给该套接字绑定IP和端口
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;//协议种类
        local.sin_port = htons(_port); //转换端口号为网络序列
        // 云服务器，不允许用户直接bind公网IP，另外, 实际正常编写的时候，我们也不会指明IP
        // local.sin_addr.s_addr = inet_addr("42.192.83.143"); //点分十进制，字符串风格[0-255].[0-255].[0-255].[0-255]
        local.sin_addr.s_addr = INADDR_ANY;//`INADDR_ANY`就是不关心是数据是从哪个IP来的，只要是发给我的这个端口，就全部接收
        
        if(bind(_listen_sockfd, (struct sockaddr*)&local, sizeof(local)) < 0){
            //绑定失败
            cerr << "bind error" <<endl;
            return false;
        }
        //cout << "bind success!" << endl;

        // 3. 因为tcp是面向连接的, a.在通信前，需要建连接 b. 然后才能通信
        //   一定有人主动建立(客户端，需要服务)，一定有人被动接受连接(服务器，提供服务)
        //   我们当前写的是一个server, 周而复始的不间断的等待客户到来
        //   我们要不断的给用户提供一个建立连接的功能
        //
        //   设置套接字是Listen状态, 本质是允许用户连接
        const int back_log = 5;
        if(listen(_listen_sockfd, back_log) <  0){
            cerr<< "listen error" << endl;
            return false;
        }
        return true;
    }
    void Start()//业务逻辑
    {
        while(true)
        {
            struct sockaddr_in peer;//用来保存客户端的信息（ip..），方便后续发回消息
            socklen_t len = sizeof(peer);
            int new_sock = accept(_listen_sockfd, (struct sockaddr*)&peer, &len);
            if(new_sock < 0){
                //申请服务失败，进入下一次申请
                continue;
            }
            //这里打印一下是哪个客户端连接
            uint16_t cli_port = ntohs(peer.sin_port);
            std::string cli_ip = inet_ntoa(peer.sin_addr);
            std::cout << "get a new link -> : [" << cli_ip << ":" << cli_port << "]# " << new_sock << std::endl;
            
            signal(SIGCHLD, SIG_IGN);//忽略子进程的退出信号，让子进程退出自动释放资源
            pid_t id = fork();
            if(id < 0)
            {
                //parent
                //do nothing
            }
            else if(id == 0)
            {
                //child
                close(_listen_sockfd);//子进程不需要监听套接字
                
                /*  前面可以不用signal，只需要加上下面这个代码
                但是父进程要等待子进程，不过不会阻塞，因为子进程立刻就退出了
                if(fork() > 0) exit(0); //退出的是子进程
                向后走的进程，其实是孙子进程，孙子进程交给了操作系统
                */
                
                ServeIO(new_sock);
                close(new_sock);
                exit(0);
            }
            else
            {
                //parent
                /*
                waitpid(id, nullptr, 0); // 不会阻塞
                */
                close(new_sock);//父进程不需要这个套接字，这个已经传给子进程了，可以关闭了
            }
        }
    }
    void ServeIO(int new_sock)
    {
        while (true)
        {
            char buffer[1024] = {0};
            memset(buffer, 0, sizeof(buffer));
            // tcp协议是一种流式服务，可以使用文件操作
            ssize_t s = read(new_sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = '\0';
                cout << "client# " << buffer << endl;

                string echo_string = ">>>server<<< ";
                echo_string += buffer;

                write(new_sock, echo_string.c_str(), echo_string.size());
            }
            else if(s == 0)//对应的客户端退出时，read就会读到0
            {
                cout << "client quit....." << endl;
                break;
            }
            else
            {
                cerr << "read error" << endl;
                break;
            }
        }
    }
    ~UdpServer()
    {
        if(_listen_sockfd >= 0)
        {
            close(_listen_sockfd);
        }
    }
private:
    int _listen_sockfd; //套接字对应的文件描述符
    string _ip;//IP地址
    uint16_t _port;//端口号
};


int main()
{
    UdpServer* my_server = new UdpServer();
    my_server->InitServer();
    my_server->Start();
    return 0;
}